﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.OracleClient;
using System.Data.SqlClient;

namespace Azalea.ORMap
{
    public class DbContext : IDbContext
    {
        private readonly IDbFactory _dbFactory;
        private readonly IDbInfo _dbInfo;

        private readonly Dictionary<IDbConnection, DateTime> _openedConnections =
            new Dictionary<IDbConnection, DateTime>();

        private DataSet _schema;

        public DbContext(IDbInfo dbInfo)
        {
            _dbInfo = dbInfo;
            _dbFactory = GetDbFactory();
        }

        #region IDbContext Members

        public IDbFactory DbFactory
        {
            get { return _dbFactory; }
        }

        public IDbInfo DbInfo
        {
            get { return _dbInfo; }
        }

        public DataSet GetDbSchema()
        {
            if (_schema == null)
            {
                _schema = new DataSet();
                string sql = DbFactory.GetSqlStatementToGetSchema();

                IDbCommand command = _dbFactory.NewCommand();
                command.CommandText = sql;
                ICommandExecutor cmdExecutor = new CommandExecutor(this);
                DataTable tables = cmdExecutor.DataTableExecute(command);

                foreach (DataRow dr in tables.Rows)
                {
                    var table = (string) dr[0];
                    var dt = new DataTable();
                    IDbCommand cmd = _dbFactory.NewCommand();
                    cmd.CommandText = string.Format("SELECT * FROM {0}", table);
                    cmd.Connection = OpenConnection(false);
                    using (cmd.Connection)
                    {
                        IDataAdapter da = _dbFactory.NewDataAdapter(cmd);
                        switch (_dbInfo.GetDbType())
                        {
                            case DatabaseTypes.SqlServer:
                                ((SqlDataAdapter) da).FillSchema(dt, SchemaType.Source);
                                break;
                            case DatabaseTypes.Oracle:
                                ((OracleDataAdapter) da).FillSchema(dt, SchemaType.Source);
                                break;
                            default:
                                throw new NotSupportedException("Only sql server and oracle are supported");
                        }
                        dt.TableName = table;
                        _schema.Tables.Add(dt);
                    }
                }
            }
            return _schema;
        }

        public DataTable GetTableSchema(string tableName)
        {
            DataTable table = GetDbSchema().Tables[tableName];
            return table;
        }

        public IDbConnection OpenConnection(bool open)
        {
            IDbConnection connection = DbFactory.NewConnection();
            connection.ConnectionString = _dbInfo.GetConnectionString();
            if (open)
            {
                connection.Open();
                _openedConnections.Add(connection, DateTime.Now);
            }
            return connection;
        }

        public void CloseConnection(IDbConnection connection)
        {
            if (connection != null && connection.State != ConnectionState.Closed)
            {
                if (connection.State != ConnectionState.Closed)
                {
                    connection.Close();
                }
                if (_openedConnections.ContainsKey(connection))
                {
                    _openedConnections.Remove(connection);
                }
            }
        }

        #endregion

        #region private members

        private IDbFactory GetDbFactory()
        {
            switch (_dbInfo.GetDbType())
            {
                case DatabaseTypes.Odbc:
                    return new DbFactoryOdbc();
                case DatabaseTypes.Oracle:
                    return new DbFactoryOracle();
                case DatabaseTypes.SqlServer:
                    return new DbFactorySqlSvr();
                case DatabaseTypes.Sqlite:
                    return new DbFactorySqlite();
                default:
                    throw new NotSupportedException("Database type is not yet supported");
            }
        }

        #endregion
    }
}